/*
 * jsonparser.c
 * 
 * Tiedosto sisältää funktiot joilla voidaan lukea JSON -tyyppisiä tiedostoja. 
 * Tämä parseri tukee hashtable, array, string ja int arvoja.
 * 
 * author: ossi
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>
#include <locale.h>
#include <wchar.h>
#include "dynarray.h"
#include "hashtable.h"
#include "jsonparser.h"

//Ossi teki kaiken

wchar_t* json_next_string(json_parser* p);
int* json_next_int(json_parser* p);
hashtable* json_next_hashtbl(json_parser* p);
dynarray* json_next_array(json_parser* p);

/* 
 * Alustaa JSON parserin
 */
json_parser* json_init(char* filename) {
    json_parser* tmp = malloc(sizeof (json_parser));

    if (tmp == NULL) {
        return NULL;
    }

    tmp->backed = false;
    tmp->buf_empty = true;
    tmp->backbuffer = 0;
    tmp->fp = fopen(filename, "r");

    if (tmp->fp == NULL) {
        free(tmp);
        return NULL;
    }
    return tmp;
}

/**
 * Vapauttaa JSON -parserin
 */
void json_dispose(json_parser* p) {
    fclose(p->fp);
    free(p);
}

/*
 * Palauttaa seuraavan merkin tiedostosta, tai edellisen
 * merkin backbufferista. 
 * @return seuraava merkki
 */
wchar_t json_next(json_parser* p) {
    if (p->backed && !p->buf_empty) {
        p->backed = false;
        return p->backbuffer;
    }

    wchar_t c = fgetwc(p->fp);

    p->backbuffer = c;
    p->buf_empty = false;
    return c;
}

/*
 * Siirtää "stream pointeria" yhden askeleen taaksepäin,
 * t.s. tämän jälkeen next palauttaa edellisen merkin. 
 */
void json_back(json_parser* p) {
    p->backed = !p->buf_empty; // jos bufferi on tyhjä, ei voida palauttaa edellistä
}

/**
 * Palauttaa seuraavan ei whitespace merkin tiedostosta
 */
wchar_t json_next_nws(json_parser* p) {
    wchar_t c = json_next(p);

    while (c != EOF && isspace((char) c)) {
        c = json_next(p);
    }

    return c;
}

/*
 * Lukee tiedostosta merkkijonon seuraavaan " merkkiin saakka
 */
wchar_t* json_next_string(json_parser* p) {

    wchar_t* tmp = malloc(sizeof (wchar_t)*(20));
    unsigned int len = 20;

    if (tmp == NULL) {
        return NULL;
    }

    wchar_t c = json_next(p);
    unsigned int top = 0;

    while (c != EOF && c != L'"') {
        if (top == len - 1) { //ei tiedetä kuinka pitkä merkkijono on, joten kasvatetaan tarpeen mukaan
            len = len * 2;
            void* tmp_p = realloc(tmp, sizeof (wchar_t) * len);
            if (tmp_p == NULL) {
                free(tmp);
                return NULL;
            }
            tmp = (wchar_t*) tmp_p;
        }

        tmp[top] = c;
        ++top;
        c = json_next(p);
    }

    tmp[top] = 0;

    return tmp;
}

/**
 * Palauttaa seuraavan json_itemin 
 */
json_item* json_next_item(json_parser* p) {
    json_item* tmp = malloc(sizeof (json_item));
    if (tmp == NULL) {
        return NULL;
    }

    wchar_t c = json_next_nws(p);

    switch (c) {
        case L'"':
            tmp->data = json_next_string(p);
            tmp->type = TYPE_STRING;
            break;
        case L'{':
            json_back(p);
            tmp->data = json_next_hashtbl(p);
            tmp->type = TYPE_HASHTABLE;
            break;
        case L'[':
            json_back(p);
            tmp->data = json_next_array(p);
            tmp->type = TYPE_ARRAY;
            break;
        default:
            if (isdigit((char) c) || c == L'-') {
                json_back(p);
                tmp->data = json_next_int(p);
                tmp->type = TYPE_INT;
            } else {
                free(tmp);
                tmp = NULL;
            }
    }
    return tmp;
}

/*
 * Lukee integerin tiedostosta
 */
int* json_next_int(json_parser* p) {
    wchar_t c = json_next(p);
    bool negative = false;
    if (c == L'-') {
        negative = true;
    } else json_back(p);
    int* t;
    t = malloc(sizeof (int));
    if (t == NULL) return NULL;
    *t = 0;
    while (isdigit((c = json_next(p)))) {
        *t = *t * 10 + (c - L'0');
    }
    json_back(p);
    if (negative) {
        *t = -*t;
    }
    return t;
}

/*
 * Lukee tiedostosta seuraavan taulukon, muotoa [a,b,c,...]
 */
dynarray* json_next_array(json_parser* p) {
    dynarray* item_array = da_new();

    if (json_next(p) != L'[') {
        da_free(item_array);
        return NULL;
    }

    while (true) {
        switch (json_next_nws(p)) {
            case EOF:
                da_free(item_array);
                return NULL;
            case L']':
                return item_array;
            default:
                json_back(p);
                json_item* tmp = json_next_item(p);
                da_append(item_array, tmp);
                switch (json_next_nws(p)) {
                    case L',':
                        break;
                    case L']':
                        return item_array;
                }
        }
    }
}

/*
 * Lukee tiedostosta seuraavan hashtablen, muotoa {"key":value,"key":value ....}
 */
hashtable* json_next_hashtbl(json_parser* p) {
    hashtable* item_table = ht_new();

    if (json_next(p) != L'{') {
        ht_free(item_table);
        return NULL;
    }
    
    wchar_t* k;
    while (true) {
        switch (json_next_nws(p)) {
            case EOF: //tiedosto loppui yllättäen
                ht_free(item_table);
                return NULL;
            case L'}':
                return item_table;
            case L'"': // luetaan avain
                k = json_next_string(p);
                break;
            default:
                ht_free(item_table);
                return NULL;
        }
        if (json_next_nws(p) != L':') {
            free (k);
            ht_free(item_table);
            return NULL;
        }
        json_item* tmp = json_next_item(p);
        ht_put(item_table, k, tmp);
        switch (json_next_nws(p)) {
            case L',':
                break;
            case L'}':
                return item_table;
        }
    }
}

void json_item_free(json_item* ji) {
    switch (ji->type) {
        case TYPE_ARRAY: 
            da_free_json((dynarray*) ji->data);
            break;
        case TYPE_HASHTABLE: 
            ht_free((hashtable*) ji->data);
            break;
        default: free(ji->data);
    }
    free(ji);
}